-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local lu = require 'luaunit'
local defs_stub = {
    RET = {
        OK = 0,
        ERR = -1
    }
}

local function build_class_stub()
    return function()
        local cls = {}
        cls.__index = cls
        function cls:new(...)
            local instance = setmetatable({}, cls)
            if instance.ctor then
                instance:ctor(...)
            end
            return instance
        end
        return cls
    end
end

local function build_crc_module()
    return setmetatable({}, {
        __call = function(_, data)
            local sum = 0
            for i = 1, #data do
                sum = (sum + string.byte(data, i)) % 256
            end
            return sum
        end
    })
end

local function new_chip()
    local chip = {
        writes = {},
        on_write = nil,
        on_read = nil
    }

    function chip:Write(ctx, cmd, data)
        table.insert(self.writes, { cmd = cmd, data = data })
        if self.on_write then
            local ok, ret = pcall(self.on_write, cmd, data)
            if not ok then
                error(ret)
            end
            return ret
        end
        return true
    end

    function chip:Read(ctx, cmd, len)
        if self.on_read then
            return self.on_read(cmd, len)
        end
        error('read handler not set')
    end

    return chip
end

local function set_lsb(value)
    if value % 2 == 0 then
        return value + 1
    end
    return value
end

local function build_read_payload(addr, cmd, data_str)
    local crc8 = package.loaded['mc.crc8']
    local head = string.char(addr, cmd, set_lsb(addr))
    local crc = crc8(head .. data_str)
    return data_str .. string.char(crc)
end

TestSd500x = {}

function TestSd500x:setUp()
    package.loaded['independent_vrd.chip.sd500x_pmbus'] = nil
    package.loaded['mc.class'] = build_class_stub()
    package.loaded['mc.context'] = {
        get_context_or_default = function()
            return {}
        end,
        new = function()
            return {}
        end
    }
    package.loaded['mc.logging'] = {
        notice = function() end,
        info = function() end,
        debug = function() end,
        error = function() end,
        maintenance = function() end,
        MLOG_INFO = 1,
        FC__PUBLIC_OK = 1
    }
    package.loaded['mc.crc8'] = build_crc_module()
    package.loaded['independent_vrd.ind_vrd_defs'] = defs_stub
    package.loaded['skynet'] = {
        sleep = function() end
    }
    package.loaded['independent_vrd.tool.parse_hex'] = function(file_path, buf_len)
        if file_path:match('invalid') then
            return { ret = -1 }
        end
        if file_path:match('short') then
            return { ret = 0, real_len = 1000, buffer = string.rep('\0', 1000) }
        end
        return { ret = 0, real_len = buf_len, buffer = string.rep('\0', buf_len) }
    end

    local sd500x = require 'independent_vrd.chip.sd500x_pmbus'
    self.sd500x_class = sd500x
end

function TestSd500x:tearDown()
    package.loaded['independent_vrd.chip.sd500x_pmbus'] = nil
    package.loaded['mc.class'] = nil
    package.loaded['mc.context'] = nil
    package.loaded['mc.logging'] = nil
    package.loaded['mc.crc8'] = nil
    package.loaded['independent_vrd.ind_vrd_defs'] = nil
    package.loaded['skynet'] = nil
    package.loaded['independent_vrd.tool.parse_hex'] = nil
end

function TestSd500x:new_device(custom)
    local obj = {
        RefChip = new_chip(),
        CompRefChip = new_chip(),
        Address = 0x20,
        CompAddress = 0x30,
        UID = 'UID-1',
        VrdId = 1,
        UpgradeFileName = {
            FirmwareFileName = '/cfg.hex',
            BootFileName = '/boot.hex'
        }
    }
    if custom then
        for k, v in pairs(custom) do
            obj[k] = v
        end
    end
    return self.sd500x_class:new({ object = obj }, 'pos1'), obj
end

function TestSd500x:test_switch_to_boot_rom_mode_success()
    local dev = self:new_device()
    local captured
    dev.obj.RefChip.on_write = function(cmd, data)
        captured = { cmd = cmd, data = data }
        return true
    end

    local ret = dev:switch_to_boot_rom_mode()
    lu.assertEquals(defs_stub.RET.OK, ret)
    lu.assertNotNil(captured)
    lu.assertEquals(0xD9, captured.cmd)
    lu.assertEquals(3, #captured.data)

    local crc8 = package.loaded['mc.crc8']
    local expected_crc = crc8(string.char(dev.obj.Address, 0xD9) .. string.char(0x5A, 0x5A))
    lu.assertEquals(expected_crc, string.byte(captured.data, 3))
end

function TestSd500x:test_switch_to_boot_rom_mode_failure()
    local dev = self:new_device()
    dev.obj.RefChip.on_write = function()
        error('write failed')
    end

    local ret = dev:switch_to_boot_rom_mode()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_erase_cfg_blocks_targets_main_and_backup()
    local dev = self:new_device()
    local offsets = {}
    local function parse_offset(payload)
        local b2 = string.byte(payload, 2)
        local b3 = string.byte(payload, 3)
        local b4 = string.byte(payload, 4)
        local b5 = string.byte(payload, 5)
        return b2 * 0x1000000 + b3 * 0x10000 + b4 * 0x100 + b5
    end

    dev.obj.RefChip.on_write = function(cmd, payload)
        if cmd == 0xF1 then
            table.insert(offsets, parse_offset(payload))
        end
        return true
    end

    local ret = dev:erase_cfg_blocks()
    lu.assertEquals(defs_stub.RET.OK, ret)
    lu.assertEquals(8, #offsets)
    lu.assertEquals(0x70000000, offsets[1])
    lu.assertEquals(0x70004000, offsets[5])
end

function TestSd500x:test_read_data_accepts_payload_with_valid_crc()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        lu.assertEquals(3, len)
        local data_str = string.char(0x41, 0x42)
        return build_read_payload(obj.Address, cmd, data_str)
    end

    local ret, data = dev:read_data(obj.RefChip, 0xAA, 2, obj.Address)
    lu.assertEquals(0, ret)
    lu.assertEquals(string.char(0x41, 0x42), data)
end

function TestSd500x:test_read_cfg_collects_all_blocks()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        return true
    end
    local blk = 0
    obj.RefChip.on_read = function(cmd, len)
        if cmd ~= 0xF9 then
            return string.rep('\0', len)
        end
        blk = blk + 1
        local datalen = len - 1
        local header = string.char(blk)
        local fill = string.rep(string.char(0x90 + blk), datalen - 1)
        return build_read_payload(obj.Address, cmd, header .. fill)
    end

    local ret, data = dev:read_cfg({
        file_len = 32,
        start_addr = 0x70000000,
        chip = obj.RefChip,
        addr = obj.Address
    })

    lu.assertEquals(0, ret)
    local expected = string.rep(string.char(0x91), 16) .. string.rep(string.char(0x92), 16)
    lu.assertEquals(expected, data)
end

function TestSd500x:test_get_version_success()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        lu.assertEquals(0xD6, cmd)
        lu.assertEquals(2, len)
        local data_str = string.char(0x42)
        return build_read_payload(obj.Address, cmd, data_str)
    end

    local version = dev:get_version()
    lu.assertEquals(0x42, version)
end

function TestSd500x:test_get_version_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        error('read failed')
    end

    local version = dev:get_version()
    lu.assertEquals('', version)
end

function TestSd500x:test_get_version_invalid_255()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        local data_str = string.char(255)
        return build_read_payload(obj.Address, cmd, data_str)
    end

    local version = dev:get_version()
    lu.assertEquals(255, version)
end

function TestSd500x:test_check_chip_accessible_success()
    local dev = self:new_device()
    local ret = dev:check_chip_accessible()
    lu.assertTrue(ret)
end

function TestSd500x:test_check_chip_accessible_no_refchip()
    local dev = self:new_device({ RefChip = false })
    local ret = dev:check_chip_accessible()
    lu.assertFalse(ret)
end

function TestSd500x:test_check_chip_accessible_no_comprefchip()
    local dev = self:new_device({ CompRefChip = false })
    local ret = dev:check_chip_accessible()
    lu.assertFalse(ret)
end

function TestSd500x:test_enter_boot_rom_already_in_boot_rom()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_read = function(cmd, len)
        if cmd == 0xEC then
            local data_str = string.char(0, 0, 1, 0, 5)
            return build_read_payload(obj.CompAddress, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local ret = dev:enter_boot_rom()
    lu.assertEquals(0, ret)
end

function TestSd500x:test_enter_boot_rom_switch_success()
    local dev, obj = self:new_device()
    local switch_count = 0
    obj.RefChip.on_write = function()
        switch_count = switch_count + 1
        return true
    end
    obj.CompRefChip.on_read = function(cmd, len)
        if cmd == 0xEC then
            if switch_count > 0 then
                local data_str = string.char(0, 0, 1, 0, 5)
                return build_read_payload(obj.CompAddress, cmd, data_str)
            end
        end
        return string.rep('\0', len)
    end

    local ret = dev:enter_boot_rom()
    lu.assertEquals(0, ret)
end

function TestSd500x:test_enter_boot_rom_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        error('write failed')
    end
    obj.CompRefChip.on_read = function()
        return string.rep('\0', 10)
    end

    local ret = dev:enter_boot_rom()
    lu.assertEquals(defs_stub.RET.ERR, ret)
end

function TestSd500x:test_check_boot_rom_version_success()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_read = function(cmd, len)
        if cmd == 0xEC then
            local data_str = string.char(0, 0, 1, 0, 5)
            return build_read_payload(obj.CompAddress, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local ret = dev:check_boot_rom_version()
    lu.assertEquals(defs_stub.RET.OK, ret)
end

function TestSd500x:test_check_boot_rom_version_failure()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_read = function(cmd, len)
        if cmd == 0xEC then
            local data_str = string.char(0, 0, 1, 0, 10)
            return build_read_payload(obj.CompAddress, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local ret = dev:check_boot_rom_version()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_write_cfg_success()
    local dev, obj = self:new_device()
    local write_count = 0
    obj.RefChip.on_write = function(cmd, data)
        if cmd == 0xF4 then
            write_count = write_count + 1
        end
        return true
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        head_len = 5,
        data = string.rep('A', 32),
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:write_cfg(config)
    lu.assertEquals(defs_stub.RET.OK, ret)
    lu.assertEquals(2, write_count)
end

function TestSd500x:test_write_cfg_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function(cmd, data)
        if cmd == 0xF4 then
            error('write failed')
        end
        return true
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        head_len = 5,
        data = string.rep('A', 32),
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:write_cfg(config)
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_read_cfg_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        return true
    end
    obj.RefChip.on_read = function()
        error('read failed')
    end

    local ret, data = dev:read_cfg({
        file_len = 32,
        start_addr = 0x70000000,
        chip = obj.RefChip,
        addr = obj.Address
    })
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_verify_data_length_mismatch()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        return true
    end
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0xF9 then
            local data_str = string.char(1) .. string.rep('\0', 15)
            return build_read_payload(obj.Address, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        data = string.rep('A', 32),
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:verify_data(config)
    lu.assertEquals(defs_stub.RET.ERR, ret)
end

function TestSd500x:test_verify_data_content_mismatch()
    local dev, obj = self:new_device()
    local test_data = string.rep('A', 32)
    obj.RefChip.on_write = function()
        return true
    end
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0xF9 then
            local data_str = string.char(1) .. string.rep('B', 15)
            return build_read_payload(obj.Address, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        data = test_data,
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:verify_data(config)
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_verify_data_success()
    local dev, obj = self:new_device()
    local test_data = string.rep('A', 32)
    obj.RefChip.on_write = function()
        return true
    end
    local block = 0
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0xF9 then
            block = block + 1
            local chunk = string.sub(test_data, (block - 1) * 16 + 1, block * 16)
            local payload = string.char(block) .. chunk
            return build_read_payload(obj.Address, cmd, payload)
        end
        return string.rep('\0', len)
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        data = test_data,
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:verify_data(config)
    lu.assertEquals(defs_stub.RET.OK, ret)
end

function TestSd500x:test_write_file_data_write_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        error('write failed')
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        head_len = 5,
        data = string.rep('A', 32),
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:write_file_data(config)
    lu.assertEquals(defs_stub.RET.ERR, ret)
end

function TestSd500x:test_write_file_data_verify_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        return true
    end
    obj.RefChip.on_read = function()
        error('read failed')
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        head_len = 5,
        data = string.rep('A', 32),
        chip = obj.RefChip,
        addr = obj.Address
    }
    local ret = dev:write_file_data(config)
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_write_file_data_success()
    local dev = self:new_device()
    local sequence = {}
    function dev:write_cfg(cfg)
        table.insert(sequence, 'write')
        return defs_stub.RET.OK
    end
    function dev:verify_data(cfg)
        table.insert(sequence, 'verify')
        return defs_stub.RET.OK
    end

    local config = {
        file_len = 32,
        start_addr = 0x70000000,
        head_len = 5,
        data = string.rep('A', 32),
        chip = {},
        addr = 0x20
    }
    local ret = dev:write_file_data(config)
    lu.assertEquals(defs_stub.RET.OK, ret)
    lu.assertEquals({ 'write', 'verify' }, sequence)
end

function TestSd500x:test_write_firmware_to_flash_failure()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_write = function()
        error('write failed')
    end

    local ret = dev:write_firmware_to_flash(string.rep('F', 48 * 1024))
    lu.assertEquals(defs_stub.RET.ERR, ret)
end

function TestSd500x:test_write_firmware_to_flash_success()
    local dev = self:new_device()
    local write_file_count = 0
    function dev:write_data()
        return defs_stub.RET.OK
    end
    function dev:write_file_data()
        write_file_count = write_file_count + 1
        return defs_stub.RET.OK
    end

    local ret = dev:write_firmware_to_flash(string.rep('F', 1024))
    lu.assertEquals(defs_stub.RET.OK, ret)
    lu.assertEquals(1, write_file_count)
end

function TestSd500x:test_switch_to_app_mode_success()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_write = function(cmd, data)
        lu.assertEquals(0xF0, cmd)
        return true
    end

    local ret = dev:switch_to_app_mode()
    lu.assertEquals(0, ret)
end

function TestSd500x:test_switch_to_app_mode_failure()
    local dev, obj = self:new_device()
    obj.CompRefChip.on_write = function()
        error('write failed')
    end

    local ret = dev:switch_to_app_mode()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_get_app_die_id_read_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function()
        error('read failed')
    end

    local ret = dev:get_app_die_id()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_get_app_die_id_insufficient_data()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0xC0 then
            return build_read_payload(obj.Address, cmd, string.rep('\0', 10))
        end
        return string.rep('\0', len)
    end

    local ret = dev:get_app_die_id()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_get_app_die_id_invalid_length()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0xC0 then
            local data_str = string.char(25)
            return build_read_payload(obj.Address, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local ret = dev:get_app_die_id()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_read_fw_version_success()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        if cmd == 0x9B then
            lu.assertEquals(6, len)
            local data_str = string.char(0, 1, 2, 3, 4)
            return build_read_payload(obj.Address, cmd, data_str)
        end
        return string.rep('\0', len)
    end

    local ret = dev:read_fw_version()
    lu.assertEquals(0, ret)
end

function TestSd500x:test_read_fw_version_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function()
        error('read failed')
    end

    local ret = dev:read_fw_version()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_cfg_no_firmware_file()
    local dev = self:new_device({ UpgradeFileName = { FirmwareFileName = '', BootFileName = '/boot.hex' } })
    local ret = dev:upgrade_cfg('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_cfg_parse_failure()
    local dev = self:new_device()
    local original_parse_hex = package.loaded['independent_vrd.tool.parse_hex']
    package.loaded['independent_vrd.tool.parse_hex'] = function()
        return { ret = -1 }
    end

    local ret = dev:upgrade_cfg('/test/dir/invalid')
    lu.assertEquals(-1, ret)
    
    package.loaded['independent_vrd.tool.parse_hex'] = original_parse_hex
end

function TestSd500x:test_upgrade_cfg_erase_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function(cmd, data)
        if cmd == 0xF1 then
            error('erase failed')
        end
        return true
    end

    local ret = dev:upgrade_cfg('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_firmware_chip_check_failure()
    local dev = self:new_device({ RefChip = nil })
    local ret = dev:upgrade_firmware('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_firmware_no_boot_file()
    local dev = self:new_device({ UpgradeFileName = { FirmwareFileName = '/cfg.hex', BootFileName = '' } })
    local ret = dev:upgrade_firmware('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_firmware_parse_failure()
    local dev = self:new_device()
    local original_parse_hex = package.loaded['independent_vrd.tool.parse_hex']
    package.loaded['independent_vrd.tool.parse_hex'] = function()
        return { ret = -1 }
    end

    local ret = dev:upgrade_firmware('/test/dir/invalid')
    lu.assertEquals(-1, ret)
    
    package.loaded['independent_vrd.tool.parse_hex'] = original_parse_hex
end

function TestSd500x:test_upgrade_firmware_enter_boot_rom_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        error('write failed')
    end
    obj.CompRefChip.on_read = function()
        return string.rep('\0', 10)
    end

    local ret = dev:upgrade_firmware('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_upgrade_cfg_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function(cmd, data)
        if cmd == 0xF1 then
            error('erase failed')
        end
        return true
    end

    local ret = dev:upgrade('/test/dir/')
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_valid_vrd_success()
    local dev = self:new_device()
    local ret = dev:valid_vrd()
    lu.assertEquals(0, ret)
    lu.assertEquals(1, dev.obj.ValidateReg)
end

function TestSd500x:test_valid_vrd_failure()
    local dev = self:new_device()
    local obj = dev.obj
    setmetatable(obj, {
        __newindex = function(t, k, v)
            if k == 'ValidateReg' then
                error('cannot set ValidateReg')
            end
            rawset(t, k, v)
        end
    })

    local ret = dev:valid_vrd()
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_read_data_retry_success()
    local dev, obj = self:new_device()
    local attempt = 0
    obj.RefChip.on_read = function(cmd, len)
        attempt = attempt + 1
        if attempt < 3 then
            error('read failed')
        end
        local data_str = string.char(0x41, 0x42)
        return build_read_payload(obj.Address, cmd, data_str)
    end

    local ret, data = dev:read_data(obj.RefChip, 0xAA, 2, obj.Address)
    lu.assertEquals(0, ret)
    lu.assertEquals(string.char(0x41, 0x42), data)
end

function TestSd500x:test_read_data_crc_mismatch()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        local data_str = string.char(0x41, 0x42)
        local wrong_crc = 0xFF
        return data_str .. string.char(wrong_crc)
    end

    local ret = dev:read_data(obj.RefChip, 0xAA, 2, obj.Address)
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_read_data_insufficient_payload()
    local dev, obj = self:new_device()
    obj.RefChip.on_read = function(cmd, len)
        return string.char(0x41)
    end

    local ret = dev:read_data(obj.RefChip, 0xAA, 2, obj.Address)
    lu.assertEquals(-1, ret)
end

function TestSd500x:test_write_data_retry_success()
    local dev, obj = self:new_device()
    local attempt = 0
    obj.RefChip.on_write = function(cmd, data)
        attempt = attempt + 1
        if attempt < 3 then
            error('write failed')
        end
        return true
    end

    local ret = dev:write_data(obj.RefChip, 0xAA, string.char(0x41, 0x42), obj.Address)
    lu.assertEquals(0, ret)
end

function TestSd500x:test_write_data_failure()
    local dev, obj = self:new_device()
    obj.RefChip.on_write = function()
        error('write failed')
    end

    local ret = dev:write_data(obj.RefChip, 0xAA, string.char(0x41, 0x42), obj.Address)
    lu.assertEquals(-1, ret)
end